-
-
Notifications
You must be signed in to change notification settings - Fork 442
Implement OCPP 1.6 Security #1854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
e9b8666 to
81ba853
Compare
|
@juherr i will take a look at this ASAP when i am finished with some other duties. on a more general note: i had a conversation with @florinmandache after his initial big PR. he said that he has many things going on and is short on time, and he wanted to contribute on good will. since he is a busy person, he was not interested in follow-up, review rounds and eventual clean-ups (i.e. the usual process of PR and reviewing). due to the substantial nature of the contribution, i accepted it as-is and took it upon me to do whatever is necessary to bring these features to main branch. in this context, i thank you for doing one part of it. |
|
@goekay Thanks for the clarification. I think Gemini did an interesting initial job exploring the different topics, but it definitely needs more work based on what I’ve seen so far (especially regarding OCPP 1.6 Security and OICP). Since OCPI and OCPP 2.x are an even tougher challenge, and my time is just as limited, I’ll let you take the first shot on those ones 😉 |
# Conflicts: # src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java # src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
|
FYI @juherr: steve-community/ocpp-jaxb#18 naming of the json schema files differ a bit from the original (see links from comment). do you know why? |
|
Ah, you mean the request suffix in some filenames? |
* clean up resources folder and pom file * and do necessary changes for project to compile
|
i did some "low-hanging fruit" refactoring to reduce the size of the PR (i.e. moving the data models and their generation to ocpp-jaxb library). will do some more. i think this is very important in order to able to properly review the PR and separate the good parts from inaccurate AI slop. while being on this... i did a litmus test and tried to understand how "http basic auth" (security profile 1) is supposed to work in the impl. ChargePointRepository has 2 new methods moreover, there is no change in |
* and also make getRegistrationStatus return the password additionally, in order prevent another DB lookup later
036e217 to
3878731
Compare
|
WIP - Implementation status of new operations
|
|
ignoring and removing the following method completely, since it has no correlation with reality IMO: the
the table actually continues in the next page with more types, but there is no type with INFO, ATTACK (heh?), BREACH (what?) etc. so this whole thing is made up. moreover, the PR invents new types (that are not in the list), such as these usages feel also wrong. actually, the motivation is nice: if there is an error during the signing of certificate, create a security event and store it in DB. but, these security events are NOT coming from station, and intermixing these with regular events is problematic IMO. @juherr do you happen to know whether these are retrofits coming from ocpp 2.x ? |
I haven’t looked deeply into OCPP 2.x or even the current PR once I understood where it originated from. According to the whitepaper, only critical events are supposed to be sent by the station — and I believe all of those should indeed be stored. |
|
so, i can assume that these additions were a mistake and i can remove them
? |
* various simplifications and improvements * certificate is still as is. this is a future TODO
db70668 to
59c1c8d
Compare
* remove unnecessary and invalid parts
|
In my opinion, security events triggered by the backend are a separate concern, and a warning log should be enough for now. |
d54af17 to
d84e47c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some remarks after a first review of the changes.
I should be able to make them by myself as it comes from my branch, but I would like your review first.
| // ------------------------------------------------------------------------- | ||
|
|
||
| public void extendedTriggerMessage(ChargePointSelect cp, ExtendedTriggerMessageTask task) { | ||
| if (cp.isJson()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to throw an exception when the CP isn’t JSON, rather than silently ignoring the case. This would make misconfigurations easier to detect.
| var signedCertificatePem = certificateSigningService.signCertificateRequest(csr, chargeBoxIdentity); | ||
| var caCertificatePem = certificateSigningService.getCertificateChain(); | ||
| var certificateChain = signedCertificatePem + caCertificatePem; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be grouped into a single method in the service that returns the signedCertificatePem and the certificateChain length
| try { | ||
| if (parameters.getRequestId() == null) { | ||
| log.warn("No requestId in {}", parameters); | ||
| } else { | ||
| securityRepository.insertLogUploadStatus( | ||
| chargeBoxIdentity, | ||
| parameters.getRequestId(), | ||
| parameters.getStatus().value(), | ||
| DateTime.now() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| try { | |
| if (parameters.getRequestId() == null) { | |
| log.warn("No requestId in {}", parameters); | |
| } else { | |
| securityRepository.insertLogUploadStatus( | |
| chargeBoxIdentity, | |
| parameters.getRequestId(), | |
| parameters.getStatus().value(), | |
| DateTime.now() | |
| ); | |
| } | |
| if (parameters.getRequestId() == null) { | |
| throw new InvalidArgumentException("RequestId is null"); | |
| } | |
| try { | |
| securityRepository.insertLogUploadStatus( | |
| chargeBoxIdentity, | |
| parameters.getRequestId(), | |
| parameters.getStatus().value(), | |
| DateTime.now() | |
| ); | |
| } |
| try { | ||
| if (parameters.getRequestId() == null) { | ||
| log.warn("No requestId in {}", parameters); | ||
| } else { | ||
| securityRepository.insertFirmwareUpdateStatus( | ||
| chargeBoxIdentity, | ||
| parameters.getRequestId(), | ||
| parameters.getStatus().value(), | ||
| DateTime.now() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| try { | |
| if (parameters.getRequestId() == null) { | |
| log.warn("No requestId in {}", parameters); | |
| } else { | |
| securityRepository.insertFirmwareUpdateStatus( | |
| chargeBoxIdentity, | |
| parameters.getRequestId(), | |
| parameters.getStatus().value(), | |
| DateTime.now() | |
| ); | |
| } | |
| if (parameters.getRequestId() == null) { | |
| throw new InvalidArgumentException("RequestId is null"); | |
| } | |
| try { | |
| securityRepository.insertFirmwareUpdateStatus( | |
| chargeBoxIdentity, | |
| parameters.getRequestId(), | |
| parameters.getStatus().value(), | |
| DateTime.now() | |
| ); | |
| } |
| String keystorePath = securityConfig.getKeystorePath(); | ||
| String keystorePassword = securityConfig.getKeystorePassword(); | ||
| String keystoreType = securityConfig.getKeystoreType(); | ||
|
|
||
| KeyStore keystore = KeyStore.getInstance(keystoreType); | ||
| try (FileInputStream fis = new FileInputStream(keystorePath)) { | ||
| keystore.load(fis, keystorePassword.toCharArray()); | ||
| } | ||
|
|
||
| String alias = keystore.aliases().nextElement(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| String keystorePath = securityConfig.getKeystorePath(); | |
| String keystorePassword = securityConfig.getKeystorePassword(); | |
| String keystoreType = securityConfig.getKeystoreType(); | |
| KeyStore keystore = KeyStore.getInstance(keystoreType); | |
| try (FileInputStream fis = new FileInputStream(keystorePath)) { | |
| keystore.load(fis, keystorePassword.toCharArray()); | |
| } | |
| String alias = keystore.aliases().nextElement(); | |
| var keystorePath = securityConfig.getKeystorePath(); | |
| var keystorePassword = securityConfig.getKeystorePassword(); | |
| var keystoreType = securityConfig.getKeystoreType(); | |
| var keystore = KeyStore.getInstance(keystoreType); | |
| try (var fis = new FileInputStream(keystorePath)) { | |
| keystore.load(fis, keystorePassword.toCharArray()); | |
| } | |
| var alias = keystore.aliases().nextElement(); |
| @Size(max = 10000, message = "Certificate chain must not exceed {max} characters") | ||
| @Schema(description = "PEM-encoded certificate chain (signed certificate + CA certificate)", | ||
| requiredMode = Schema.RequiredMode.REQUIRED, maxLength = 10000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| @Size(max = 10000, message = "Certificate chain must not exceed {max} characters") | |
| @Schema(description = "PEM-encoded certificate chain (signed certificate + CA certificate)", | |
| requiredMode = Schema.RequiredMode.REQUIRED, maxLength = 10000) | |
| @Size(max = 10_000, message = "Certificate chain must not exceed {max} characters") | |
| @Schema(description = "PEM-encoded certificate chain (signed certificate + CA certificate)", | |
| requiredMode = Schema.RequiredMode.REQUIRED, maxLength = 10_000) |
| @Size(max = 5500, message = "Certificate must not exceed {max} characters") | ||
| @Schema(description = "PEM-encoded X.509 certificate", requiredMode = Schema.RequiredMode.REQUIRED, | ||
| maxLength = 5500) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| @Size(max = 5500, message = "Certificate must not exceed {max} characters") | |
| @Schema(description = "PEM-encoded X.509 certificate", requiredMode = Schema.RequiredMode.REQUIRED, | |
| maxLength = 5500) | |
| @Size(max = 5_500, message = "Certificate must not exceed {max} characters") | |
| @Schema(description = "PEM-encoded X.509 certificate", requiredMode = Schema.RequiredMode.REQUIRED, | |
| maxLength = 5_500) |
| @Size(max = 5000, message = "Firmware signature must not exceed {max} characters") | ||
| @Schema(description = "Cryptographic signature of the firmware file", | ||
| requiredMode = Schema.RequiredMode.REQUIRED, maxLength = 5000) | ||
| private String firmwareSignature; | ||
|
|
||
| @Size(max = 5500, message = "Signing certificate must not exceed {max} characters") | ||
| @Schema(description = "PEM-encoded certificate used to sign the firmware", maxLength = 5500) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| @Size(max = 5000, message = "Firmware signature must not exceed {max} characters") | |
| @Schema(description = "Cryptographic signature of the firmware file", | |
| requiredMode = Schema.RequiredMode.REQUIRED, maxLength = 5000) | |
| private String firmwareSignature; | |
| @Size(max = 5500, message = "Signing certificate must not exceed {max} characters") | |
| @Schema(description = "PEM-encoded certificate used to sign the firmware", maxLength = 5500) | |
| @Size(max = 5_000, message = "Firmware signature must not exceed {max} characters") | |
| @Schema(description = "Cryptographic signature of the firmware file", | |
| requiredMode = Schema.RequiredMode.REQUIRED, maxLength = 5_000) | |
| private String firmwareSignature; | |
| @Size(max = 5_500, message = "Signing certificate must not exceed {max} characters") | |
| @Schema(description = "PEM-encoded certificate used to sign the firmware", maxLength = 5_500) |
| <td> | ||
| <c:choose> | ||
| <c:when test="${event.severity == 'CRITICAL' || event.severity == 'HIGH'}"> | ||
| <span style="color: red; font-weight: bold;">${event.severity}</span> | ||
| </c:when> | ||
| <c:when test="${event.severity == 'MEDIUM'}"> | ||
| <span style="color: orange; font-weight: bold;">${event.severity}</span> | ||
| </c:when> | ||
| <c:otherwise> | ||
| ${event.severity} | ||
| </c:otherwise> | ||
| </c:choose> | ||
| </td> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following the latest discussion, it should be removed as there is no longer severity
| <select name="limit"> | ||
| <option value="50" ${limit == 50 ? 'selected' : ''}>50</option> | ||
| <option value="100" ${limit == 100 ? 'selected' : ''}>100</option> | ||
| <option value="250" ${limit == 250 ? 'selected' : ''}>250</option> | ||
| <option value="500" ${limit == 500 ? 'selected' : ''}>500</option> | ||
| </select> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Limit is great if available in all pages. As I remember, it is not always the case.

@florinmandache I built on your work and made the minimal required changes.
Some tests would be very welcome. I also noticed that CSMS security requests aren’t implemented yet.
@goekay It seems to be working (at least it starts correctly), but I haven’t fully tested it yet and it could use some polishing.
Feel free to rework or improve it as you see fit.
Fix #100